﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Randomize.Core.Constructors;
using Randomize.Core.Generators;

namespace Randomize.Core.Descriptors
{
    public abstract class ComplexTypeDescriptor<TComplex> : IComplexTypeDescriptor<TComplex>
    {
        private Func<TComplex> _constructor;

        public List<PropertyDescriptorBase<TComplex>> PropertyDescriptors { get; private set; }

        public ComplexTypeDescriptor()
        {
            PropertyDescriptors = new List<PropertyDescriptorBase<TComplex>>();
        }

        public IIntDescriptor Property(Expression<Func<TComplex, int>> property = null, Action<TComplex, int> setter = null)
        {
            return this.AddPropertyDescriptor<int, IIntDescriptor, IntDescriptor>(new IntDescriptor(), property, setter);
        }

        public IDoubleDescriptor Property(Expression<Func<TComplex, double>> property = null, Action<TComplex, double> setter = null)
        {
            return this.AddPropertyDescriptor<double, IDoubleDescriptor, DoubleDescriptor>(new DoubleDescriptor(), property, setter);
        }

        public IStringDescriptor Property(Expression<Func<TComplex, string>> property = null, Action<TComplex, string> setter = null)
        {
            return this.AddPropertyDescriptor<string, IStringDescriptor, StringDescriptor>(new StringDescriptor(),
                                                                                           property,
                                                                                           setter);
        }

        public IDateTimeDescriptor Property(Expression<Func<TComplex, DateTime>> property = null, Action<TComplex, DateTime> setter = null)
        {
            return this.AddPropertyDescriptor<DateTime, IDateTimeDescriptor, DateTimeDescriptor>(new DateTimeDescriptor(), property, setter);
        }

        public ICustomDescriptor<TProperty> Property<TProperty>(Expression<Func<TComplex, TProperty>> property = null, Action<TComplex, TProperty> setter = null)
        {
            return
                this.AddPropertyDescriptor<TProperty, ICustomDescriptor<TProperty>, CustomDescriptor<TProperty>>(
                    new CustomDescriptor<TProperty>(), property, setter);
        }

        public ICollectionDescriptor<TCollectionItem> Collection<TCollectionItem>(IGenerationDescriptor<TCollectionItem> itemDescriptor, Action<TComplex, IEnumerable<TCollectionItem>> setter)
        {
            return
                this.AddPropertyDescriptor
                    <IEnumerable<TCollectionItem>, ICollectionDescriptor<TCollectionItem>,
                        CollectionDescriptor<TCollectionItem>>(new CollectionDescriptor<TCollectionItem>(itemDescriptor),
                                                               null, setter);
        }

        private TDescriptor AddPropertyDescriptor<TProperty, TDescriptorInterface, TDescriptor>(TDescriptor descriptor,
            Expression<Func<TComplex, TProperty>> property,
            Action<TComplex, TProperty> setter)
            where TDescriptorInterface : class, IGenerationDescriptor<TProperty, TDescriptorInterface>
            where TDescriptor : GenerationDescriptor<TProperty, TDescriptorInterface>
        {
            var propertyDescriptor = new PropertyDescriptor<TComplex, TProperty, TDescriptorInterface>
                                     {
                                         PropertyExpression = property,
                                         Setter = setter,
                                         TypeDescriptor = descriptor
                                     };
            PropertyDescriptors.Add(propertyDescriptor);

            return descriptor;
        }

        public void CreateUsing(Func<TComplex> constructingFunction)
        {
            _constructor = constructingFunction;
        }

        public IGenerator<TComplex> CreateGenerator(Random random)
        {
            return new ComplexTypeConstructor<TComplex>(this.PropertyDescriptors
                                                            .Select(descriptor => descriptor.CreateConstructor(random))
                                                            .ToList())
            {
                ConstructingFunction = _constructor
            };
        }
    }
}
